import { ChangeDetectionStrategy, Component, NO_ERRORS_SCHEMA, ChangeDetectorRef } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';

import { GridModule } from './grid.module';
import { RowDirective } from './row.directive';
import { ColDirective } from './col.directive';
import { LvAlign, LvColSizeType, LvGutterObject, LvJustify } from './api';

/**
 * 当前单测不使用onpush策略，组件逻辑样式使用angular的host功能实现样式类变更
 * 单测中无法实现动态变更，当前单测中场景很多，每个分散使用独立实例过多
 * 该单测特殊处理
 */
@Component({
  template: `
    <div lv-row [lvGutter]="gutter" [lvAlign]="align" [lvJustify]="justify">
      <div lv-col [lvSpan]="span"></div>
      <div lv-col [lvSpan]="span"></div>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TestRowComponent {
  gutter: number | LvGutterObject | [number, number] | [LvGutterObject, LvGutterObject];
  align: LvAlign;
  justify: LvJustify;
  span: number;
}

@Component({
  template: `
    <div lv-row>
      <div
        lv-col
        [lvSpan]="span"
        [lvOffset]="offset"
        [lvOrder]="order"
        [lvPull]="pull"
        [lvPush]="push"
        [lvXs]="xs"
        [lvSm]="sm"
        [lvMd]="md"
        [lvLg]="lg"
        [lvXl]="xl"
        [lvXXl]="xxl"
      ></div>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TestColComponent {
  span: number;
  flex: string | number;
  offset: number;
  order: number;
  pull: number;
  push: number;
  xs: LvColSizeType;
  sm: LvColSizeType;
  md: LvColSizeType;
  lg: LvColSizeType;
  xl: LvColSizeType;
  xxl: LvColSizeType;
}

describe('grid', () => {
  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      imports: [GridModule, NoopAnimationsModule],
      schemas: [NO_ERRORS_SCHEMA],
      declarations: [TestRowComponent, TestColComponent],
      teardown: { destroyAfterEach: false },
    });
    TestBed.compileComponents();
  }));

  describe('render', () => {
    let fixture: ComponentFixture<TestRowComponent | TestColComponent>;
    let component: TestRowComponent | TestColComponent;
    let cdr: ChangeDetectorRef;

    const renderComponent = fakeAsync((param?) => {
      if (param) {
        for (const [key, value] of Object.entries(param)) {
          component[key] = value;
        }
      }

      fixture.detectChanges();
      tick(500);
      fixture.detectChanges();
      expect(fixture).toMatchSnapshot();
    });

    const rerenderComponent = param => {
      if (param) {
        for (const [key, value] of Object.entries(param)) {
          component[key] = value;
        }
      }
      cdr.detectChanges();
      expect(fixture).toMatchSnapshot();
    };

    it('basic', () => {
      fixture = TestBed.createComponent(TestColComponent);
      component = fixture.componentInstance;
      cdr = fixture.debugElement.injector.get(ChangeDetectorRef);
      renderComponent({ span: 24 });
      rerenderComponent({ span: 8 });
    });

    it('gutter', () => {
      fixture = TestBed.createComponent(TestRowComponent);
      component = fixture.componentInstance;
      cdr = fixture.debugElement.injector.get(ChangeDetectorRef);
      renderComponent({ gutter: 16, span: 6 });
      rerenderComponent({ gutter: { xs: 8, sm: 16, md: 24, lg: 32 }, span: 6 });
      rerenderComponent({ gutter: [16, 24], span: 6 });
    });

    it('align', () => {
      fixture = TestBed.createComponent(TestRowComponent);
      component = fixture.componentInstance;
      cdr = fixture.debugElement.injector.get(ChangeDetectorRef);
      renderComponent({ align: 'start', span: 6 });
      rerenderComponent({ align: 'center', span: 6 });
      rerenderComponent({ align: 'end', span: 6 });
    });

    it('justify', () => {
      fixture = TestBed.createComponent(TestRowComponent);
      component = fixture.componentInstance;
      cdr = fixture.debugElement.injector.get(ChangeDetectorRef);
      renderComponent({ justify: 'start', span: 6 });
      rerenderComponent({ justify: 'center', span: 6 });
      rerenderComponent({ justify: 'end', span: 6 });
      rerenderComponent({ justify: 'space-between', span: 6 });
      rerenderComponent({ justify: 'space-around', span: 6 });
      rerenderComponent({ justify: 'space-evenly', span: 6 });
    });

    it('offset', () => {
      fixture = TestBed.createComponent(TestColComponent);
      component = fixture.componentInstance;
      renderComponent({ offset: 8, span: 8 });
    });

    it('responsive', () => {
      fixture = TestBed.createComponent(TestColComponent);
      component = fixture.componentInstance;
      renderComponent({ xs: 2, sm: 4, md: 6, lg: 8, xl: 10, xxl: 8 });
    });
  });

  describe('row', () => {
    let fixture: ComponentFixture<TestRowComponent>;
    let component: TestRowComponent;
    let cdr: ChangeDetectorRef;
    let rowElement: HTMLElement;
    let colElement: HTMLElement;

    beforeEach(() => {
      fixture = TestBed.createComponent(TestRowComponent);
      component = fixture.componentInstance;
      cdr = fixture.debugElement.injector.get(ChangeDetectorRef);
      rowElement = fixture.debugElement.query(By.directive(RowDirective)).nativeElement;
      colElement = fixture.debugElement.query(By.directive(ColDirective)).nativeElement;
    });

    it('should has className', () => {
      expect(rowElement.className).toBe('lv-row');
    });

    it('should has align className', () => {
      const alignList: LvAlign[] = ['start', 'center', 'end'];
      alignList.forEach(align => {
        component.align = align;
        cdr.detectChanges();
        expect(rowElement.classList).toContain(`lv-row-align-${align}`);
      });
    });

    it('should has justify className', () => {
      const justifyList: LvJustify[] = ['start', 'end', 'center', 'space-around', 'space-between', 'space-evenly'];
      justifyList.forEach(justify => {
        component.justify = justify;
        cdr.detectChanges();
        expect(rowElement.classList).toContain(`lv-row-${justify}`);
      });
    });

    it('should gutter work', () => {
      expect(rowElement.style.cssText).toBe('');
      expect(colElement.style.cssText).toBe('');
      component.gutter = 16;
      fixture.detectChanges();
      expect(rowElement.style.cssText).toBe('margin-left: -8px; margin-right: -8px;');
      expect(colElement.style.cssText).toBe('padding-left: 8px; padding-right: 8px;');
    });

    it('should gutter array work', () => {
      expect(rowElement.style.cssText).toBe('');
      expect(colElement.style.cssText).toBe('');
      component.gutter = [16, 16];
      fixture.detectChanges();
      expect(rowElement.style.cssText).toBe('margin: -8px -8px -8px -8px;');
      expect(colElement.style.cssText).toBe('padding: 8px 8px 8px 8px;');
    });

    // TODO
    it('should gutter responsive work', () => {});
  });

  describe('col', () => {
    let fixture: ComponentFixture<TestColComponent>;
    let component: TestColComponent;
    let cdr: ChangeDetectorRef;
    let colElement: HTMLElement;
    const sizeMatch = (name: string, num: number, size?: string): boolean => {
      const prefixClass = size ? `-${size}-` : `-`;
      if (name === 'span') {
        return colElement.classList.contains(`lv-col${prefixClass}${num}`);
      } else {
        return colElement.classList.contains(`lv-col${prefixClass}${name}-${num}`);
      }
    };

    beforeEach(() => {
      fixture = TestBed.createComponent(TestColComponent);
      component = fixture.componentInstance;
      cdr = fixture.debugElement.injector.get(ChangeDetectorRef);
      colElement = fixture.debugElement.query(By.directive(ColDirective)).nativeElement;
    });

    it('should has className', () => {
      expect(colElement.className).toBe('lv-col');
    });

    it('should has property className', () => {
      const propsSizeMatch = (name: keyof TestColComponent, num: number): boolean => {
        component[name] = num;
        cdr.detectChanges();
        return sizeMatch(name, num);
      };
      expect(propsSizeMatch('span', 8)).toBe(true);
      expect(propsSizeMatch('offset', 8)).toBe(true);
      expect(propsSizeMatch('order', 8)).toBe(true);
      expect(propsSizeMatch('pull', 8)).toBe(true);
      expect(propsSizeMatch('push', 8)).toBe(true);
      expect(propsSizeMatch('xs', 8)).toBe(true);
      expect(propsSizeMatch('sm', 8)).toBe(true);
      expect(propsSizeMatch('md', 8)).toBe(true);
      expect(propsSizeMatch('lg', 8)).toBe(true);
      expect(propsSizeMatch('xl', 8)).toBe(true);
      expect(propsSizeMatch('xxl', 8)).toBe(true);
    });

    it('should has responsive size property className', () => {
      const propsSizeMatch = (num: number, size: string): boolean =>
        sizeMatch('span', num, size) &&
        sizeMatch('offset', num, size) &&
        sizeMatch('order', num, size) &&
        sizeMatch('pull', num, size) &&
        sizeMatch('push', num, size);

      component.xs = { span: 1, offset: 1, order: 1, pull: 1, push: 1 };
      component.sm = { span: 2, offset: 2, order: 2, pull: 2, push: 2 };
      component.md = { span: 3, offset: 3, order: 3, pull: 3, push: 3 };
      component.lg = { span: 4, offset: 4, order: 4, pull: 4, push: 4 };
      component.xl = { span: 5, offset: 5, order: 5, pull: 5, push: 5 };
      component.xxl = { span: 6, offset: 6, order: 6, pull: 6, push: 6 };

      fixture.detectChanges();
      expect(propsSizeMatch(1, 'xs')).toBe(true);
      expect(propsSizeMatch(2, 'sm')).toBe(true);
      expect(propsSizeMatch(3, 'md')).toBe(true);
      expect(propsSizeMatch(4, 'lg')).toBe(true);
      expect(propsSizeMatch(5, 'xl')).toBe(true);
      expect(propsSizeMatch(6, 'xxl')).toBe(true);

      component.xs = { span: 2, offset: 2, order: 2, pull: 2, push: 2 };
      cdr.detectChanges();
      expect(propsSizeMatch(1, 'xs')).toBe(false);
    });
  });
});
